/* A simple multi-target logger.
 *
 * See log.h for non-static function commentary.
 *
 * Copyright 2008 Cian Synnott.
 * 
 * $Id: log.c 19 2009-11-27 20:46:48Z cian $
 */
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <time.h>

#include "time_format.h"
#include "log.h"

struct log_destination {
  LIST_ENTRY(log_destination) entries;
  FILE *stream;
};

static LIST_HEAD(destination_list, log_destination) destinations_head;
static struct destination_list *destinations = NULL;
static int log_debug_switch = 0;

#define LOG_BUFFER_LEN 512
#define LOG_LEN 1024

/* The guts of the logger.
 *
 * Args:
 * - type: string e.g. "ERR", "WARN", ...
 * - fmt: format string as per printf
 * - args: a va_list with the arguments for fmt
 */
static void log_internal(char *type, char *fmt, va_list args) {
  struct log_destination *ld;
  char formatted_line[LOG_BUFFER_LEN];
  char full_log_line[LOG_LEN];
  char *timestamp;

  timestamp = time_formatted_by("%Y-%m-%d %H:%M:%S", 1);
  if (timestamp == NULL) return;

  vsnprintf(formatted_line, LOG_BUFFER_LEN, fmt, args);

  snprintf(full_log_line, LOG_LEN, "%s %s %s\n", timestamp, type,
    formatted_line);

  free(timestamp);

  if (destinations == NULL) {
    fprintf(stderr, "%s", full_log_line);
    return;
  } else {
    for (ld = destinations->lh_first; ld != NULL; ld = ld->entries.le_next) 
      fprintf(ld->stream, "%s", full_log_line);
  }
}

int log_register(FILE *stream) {
  struct log_destination *new;

  if (destinations == NULL) {
    destinations = &destinations_head; 
    LIST_INIT(destinations);
  }

  new = malloc(sizeof(struct log_destination));
  if (new == NULL) return -1;
  
  new->stream = stream;

  LIST_INSERT_HEAD(destinations, new, entries);

  return 0;
}

int log_unregister(FILE *stream) {
  struct log_destination *ld;

  if (destinations == NULL) {
    return -1;
  }

  for (ld = destinations->lh_first; ld != NULL; ld = ld->entries.le_next) {
    if (ld->stream == stream) {
      LIST_REMOVE(ld, entries);
    }
  }

  return 0;
}

void log_set_debug(void) {
  log_debug_switch = 1;
}

void log_err(char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  log_internal("ERR", fmt, args);
  va_end(args);
}

void log_errx(int retval, char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  log_internal("ERR", fmt, args);
  va_end(args);
  exit(retval);
}

void log_warn(char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  log_internal("WARN", fmt, args);
  va_end(args);
}

void log_info(char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  log_internal("INFO", fmt, args);
  va_end(args);
}

void log_debug(char *fmt, ...) {
  va_list args;

  if (!log_debug_switch) return;

  va_start(args, fmt);
  log_internal("DEBUG", fmt, args);
  va_end(args);
}
